package com.ctbri.concurrency.ex29;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

class Toast {
	public enum Status { DRY, BUTTERED, JAMMED, FINISHED }
	private Status status = Status.DRY;
	private final int id;
	public Toast(int id) {
		this.id = id;
	}
	public void butter() {
		status = (status == Status.DRY ? Status.BUTTERED : Status.FINISHED);
	}
	public void jam() {
		status = (status == Status.DRY ? Status.JAMMED : Status.FINISHED);
	}
	public Status getStatus() {
		return status;
	}
	public int getId() {
		return id;
	}
	public String toString() {
		return "Toast " + id + ": " + status;
	}
}

class ToastQueue extends LinkedBlockingQueue<Toast> {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
}

class Toaster implements Runnable {
	private ToastQueue toastQueue;
	private int count = 0;
	private Random random = new Random(47);
	public Toaster(ToastQueue tq) {
		toastQueue = tq;
	}
	@Override
	public void run() {
		try {
			while(!Thread.interrupted()) {
				TimeUnit.MILLISECONDS.sleep(100 + random.nextInt(500));
				Toast t = new Toast(count++);
				System.out.println(t);
				toastQueue.put(t);
			}
		} catch (InterruptedException e) {
			System.out.println("Toaster interrupted");
		}
		System.out.println("Toaster off");
	}
}

class Butterer implements Runnable {
	private ToastQueue dryQueue, butteredQueue, jammedQueue, finishedQueue;
	public Butterer(ToastQueue dry, ToastQueue buttered, ToastQueue jammed, ToastQueue finished) {
		dryQueue = dry;
		butteredQueue = buttered;
		jammedQueue = jammed;
		finishedQueue = finished;
	}
	@Override
	public void run() {
		try{
			boolean switchFlag = false;
			while(!Thread.interrupted()) {
				Toast t;
				if(!switchFlag)
					t = dryQueue.poll(50, TimeUnit.MILLISECONDS);
				else
					t = jammedQueue.poll(50, TimeUnit.MILLISECONDS);
				switchFlag = !(switchFlag);
				if(t == null)
					continue;
				t.butter();
				System.out.println(t);
				if(t.getStatus() == Toast.Status.BUTTERED)
					butteredQueue.put(t);
				else
					finishedQueue.put(t);
			}
		} catch (InterruptedException e) {
			System.out.println("Butterer interrupted");
		}
		System.out.println("Butterer off");
	}
}

class Jammer implements Runnable {
	private ToastQueue dryQueue, butteredQueue, jammedQueue, finishedQueue;
	public Jammer(ToastQueue dry, ToastQueue buttered, ToastQueue jammed, ToastQueue finished) {
		dryQueue = dry;
		butteredQueue = buttered;
		jammedQueue = jammed;
		finishedQueue = finished;
	}
	@Override
	public void run() {
		try{
			boolean switchFlag = false;
			while(!Thread.interrupted()) {
				Toast t;
				if(!switchFlag)
					t = butteredQueue.poll(50, TimeUnit.MILLISECONDS);
				else
					t = dryQueue.poll(50, TimeUnit.MILLISECONDS);
				switchFlag = !(switchFlag);
				if(t == null)
					continue;
				t.jam();
				System.out.println(t);
				if(t.getStatus() == Toast.Status.JAMMED)
					jammedQueue.put(t);
				else
					finishedQueue.put(t);
			}
		} catch(InterruptedException e) {
			System.out.println("Jammer interrupted");
		}
		System.out.println("Jammer off");
	}
}

class Eater implements Runnable {
	private ToastQueue finishedQueue;
	private int counter = 0;
	public Eater(ToastQueue finished) {
		finishedQueue = finished;
	}
	public void run() {
		try{
			while(!Thread.interrupted()) {
				Toast t = finishedQueue.take();
				if(t.getId() != counter++
						|| t.getStatus() != Toast.Status.FINISHED) {
					System.out.println(">>>>> Error: " + t);
					System.exit(1);
				} else
					System.out.println("Chomp! " + t);
			}
		} catch (InterruptedException e) {
			System.out.println("Eater interrupted");
		}
		System.out.println("Eater off");
	}
}
public class ToastMatic {

	public static void main(String[] args) throws InterruptedException {
		ToastQueue dryQueue = new ToastQueue(),
					butteredQueue = new ToastQueue(),
					jammedQueue = new ToastQueue(),
					finishedQueue = new ToastQueue();
		ExecutorService exec = Executors.newCachedThreadPool();
		exec.execute(new Toaster(dryQueue));
		exec.execute(new Butterer(dryQueue, butteredQueue, jammedQueue, finishedQueue));
		exec.execute(new Jammer(dryQueue, butteredQueue, jammedQueue, finishedQueue));
		exec.execute(new Eater(finishedQueue));
		TimeUnit.SECONDS.sleep(5);
		exec.shutdownNow();
	}

}
